package com.fount4j.generator.util;

import com.fount4j.generator.constant.GeneratorConstants;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 生成文件的工具类
 *
 * @author Morven 2016/12/22
 */
public final class GeneratorFileUtils {

    private GeneratorFileUtils() {}

    /**
     * 根据配置的根路径以及包路径和文件名，创建要生成的文件对象
     *
     * @param resourcePath 文件目录
     * @param packageName  文件包目录
     * @param fileName     文件名
     * @return 目标文件对象
     */
    public static File getFile(String resourcePath, String packageName, String fileName) {
        File path;
        if (StringUtils.isBlank(resourcePath)) {
            path = new File(PathUtils.getProjectDir());
        } else {
            path = new File(resourcePath);
        }
        if (StringUtils.isNotBlank(packageName)) {
            String packageDir = packageName.replace('.', File.separatorChar);
            path = new File(path, packageDir);
        }
        path = new File(path, fileName);
        return path;
    }

    /**
     * 读取文件中的自定义内容
     *
     * @param file 目标文件
     * @return 自定义内容
     * @throws IOException e
     */
    public static String getCustomContent(File file) throws IOException {
        if (!file.exists()) {
            return null;
        }
        String content = readFileToString(file, GeneratorConstants.GLOBAL_ENCODING);
        int startIndex = content.indexOf("<custom>");
        int endIndex = content.indexOf("</custom>");
        if (startIndex > -1 && endIndex > startIndex) {
            int startNewLine = content.substring(0, startIndex).lastIndexOf('\n') + 1;
            int endNewLine = content.indexOf('\r', endIndex);
            if (endNewLine < 0) {
                endNewLine = content.indexOf('\n', endIndex);
            }
            return content.substring(startNewLine, endNewLine);
        }
        return null;
    }

    /**
     * 读取文件中导入的哪些类
     *
     * @param file 目标文件
     * @return 导入的类集合
     * @throws IOException e
     */
    public static List<String> getImports(File file) throws IOException {
        List<String> imports = new ArrayList<>();
        if (!file.exists()) {
            return imports;
        }
        String content = readFileToString(file, GeneratorConstants.GLOBAL_ENCODING);
        Pattern pattern = Pattern.compile("[ \\n\\r\\t]+import[ \\t]+(.*)[ \\t]*;[ \\t]*");
        Matcher matcher = pattern.matcher(content);

        while (matcher.find()) {
            imports.add(matcher.group(1));
        }
        return imports;
    }

    /**
     * 文件读取为字符串
     *
     * @param file     文件对象
     * @param encoding 编码格式
     * @return 文件内容
     * @throws IOException e
     */
    public static String readFileToString(File file, String encoding) throws IOException {
        FileInputStream fis = null;
        InputStreamReader isr = null;
        StringWriter sw = new StringWriter();
        try {
            fis = new FileInputStream(file);
            isr = new InputStreamReader(fis, encoding);
            char[] buffer = new char[2048];
            int n;
            while (-1 != (n = isr.read(buffer))) {
                sw.write(buffer, 0, n);
            }
        } catch (IOException e) {
            throw e;
        } finally {
            if (isr != null) {
                isr.close();
            }
            if (fis != null) {
                fis.close();
            }
        }
        return sw.toString();
    }

    /**
     * 将字符串写入文件
     *
     * @param file     目标文件
     * @param content  文本内容
     * @param encoding 编码格式
     * @throws IOException e
     */
    public static void writeStringToFile(File file, String content, String encoding) throws IOException {
        if (content == null) {
            return;
        }
        FileOutputStream fos = null;
        try {
            fos = openOutputStream(file, false);
            fos.write(content.getBytes(encoding == null ? Charset.defaultCharset() : Charset.forName(encoding)));
            fos.flush();
        } catch (IOException e) {
            throw e;
        } finally {
            if (fos != null) {
                fos.close();
            }
        }
    }

    /**
     * Opens a {@link FileOutputStream} for the specified file, checking and
     * creating the parent directory if it does not exist.
     * <p>
     * At the end of the method either the stream will be successfully opened,
     * or an exception will have been thrown.
     * <p>
     * The parent directory will be created if it does not exist.
     * The file will be created if it does not exist.
     * An exception is thrown if the file object exists but is a directory.
     * An exception is thrown if the file exists but cannot be written to.
     * An exception is thrown if the parent directory cannot be created.
     *
     * @param file   the file to open for output, must not be {@code null}
     * @param append if {@code true}, then bytes will be added to the
     *               end of the file rather than overwriting
     * @return a new {@link FileOutputStream} for the specified file
     * @throws IOException if the file object is a directory
     * @throws IOException if the file cannot be written to
     * @throws IOException if a parent directory needs creating but that fails
     * @since 2.1
     */
    public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException {
        if (file.exists()) {
            if (file.isDirectory()) {
                throw new IOException("File '" + file + "' exists but is a directory");
            }
            if (file.canWrite() == false) {
                throw new IOException("File '" + file + "' cannot be written to");
            }
        } else {
            final File parent = file.getParentFile();
            if (parent != null) {
                if (!parent.mkdirs() && !parent.isDirectory()) {
                    throw new IOException("Directory '" + parent + "' could not be created");
                }
            }
        }
        return new FileOutputStream(file, append);
    }
}
